package com.yuefresh.sys;

import java.io.Serializable;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

import javax.servlet.http.HttpServletRequest;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Service;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import com.yuefresh.util.ResourceUtil;
import com.yuefresh.util.XMLUtil;

/**
 * A SecurityService determines which roles a user is required to have to access
 * a resource.
 * 
 * Two services are provided with IVOSecurity by default,
 * {@link com.ivo.security.service.PathService}
 */
@SuppressWarnings("unchecked")
@Service
public class SecurityService implements Serializable {
	private static final long serialVersionUID = 1L;

	private Log log = LogFactory.getLog(SecurityService.class);

	private PathMapper pathMapper = new CachedPathMapper(new HashMap(), new HashMap());

	private Map<String, Object> paths = new HashMap<String, Object>();

	String configFileLocation = "classpath:security-paths.xml";

	public SecurityService() {
		configurePathMapper();
	}

	/**
	 * Configure the path mapper by xml file
	 */
	private void configurePathMapper() {
		try {
			DocumentBuilderFactory factory = DocumentBuilderFactory
					.newInstance();
			URL fileUrl = ResourceUtil.getURL(configFileLocation);

			if (fileUrl == null)
				fileUrl = ResourceUtil.getURL("/" + configFileLocation);

			if (fileUrl == null)
				return;
			// Parse document
			org.w3c.dom.Document doc = factory.newDocumentBuilder().parse(
					fileUrl.toString());
			// Get list of actions
			Element root = doc.getDocumentElement();
			NodeList pathNodes = root.getElementsByTagName("path");
			// Build list of views
			for (int i = 0; i < pathNodes.getLength(); i++) {
				Element path = (Element) pathNodes.item(i);

				String pathName = path.getAttribute("name");
				String roleNames = XMLUtil.getContainedText(path, "role-name");
				String urlPattern = XMLUtil.getContainedText(path,
						"url-pattern");

				if (roleNames != null && urlPattern != null) {
					List rolesArr = parseRoles(roleNames);
					paths.put(pathName, rolesArr);
					pathMapper.put(pathName, urlPattern);
				}
			}
		} catch (Exception ex) {
			log.error(ex);
		}
	}

	protected List parseRoles(String roleNames) {
		StringTokenizer st = new StringTokenizer(roleNames, ",; \t\n", false);
		List roles = new ArrayList();
		while (st.hasMoreTokens()) {
			roles.add(st.nextToken());
		}
		return roles;
	}

	public Set getRequiredRoles(HttpServletRequest request, String originalURL) {
		String servletPath = originalURL;// request.getServletPath();
		return getRequiredRoles(servletPath);
	}

	public Set getRequiredRoles(String servletPath) {
		Set requiredRoles = new HashSet();
		// then check path mappings first and add any required roles
		Collection constraintMatches = pathMapper.getAll(servletPath);

		for (Iterator iterator = constraintMatches.iterator(); iterator
				.hasNext();) {
			String constraint = (String) iterator.next();
			List rolesForPath = (List) paths.get(constraint);
			for (Iterator it = rolesForPath.iterator(); it.hasNext();) {
				Object role = it.next();
				if (!requiredRoles.contains(role)) {// since requiredRoles is a
													// set, isn't this useless?
					requiredRoles.add(role);
				}
			}
		}

		return requiredRoles;
	}
}
